#pragma once
// configure reader.
// for reading the following config.
/*
 // config:

	<main>
	    # this is a variable.
		a = 1
		<application>
		  // this application a.
		  a = 1
		  <me>
			a = 1
		  </me>
		</application>

		b = 1,2,3
		c = 1:2,2:3
	</main>

// parse and read like:
		CConfig conf;
		if (conf.Parse("./conf.conf")) {
		    auto a = conf.GetNode<int>("Main/a");
			auto a = conf.GetNode<bool>("Main/application/me/a");
		}
*/
#include <stdexcept>
#include <string>
#include <map>
#include <vector>
#include <utility>
#include <stack>
#include "util/defer.hpp"
#include <string.h>
#include <list>

namespace anet {
	namespace utils {
		// trim right character c.
		inline static std::string trimRight(const std::string& strSrc, char c) {
			const char* pEnd = strSrc.c_str() + strSrc.length();
			pEnd--;
			while (*pEnd == c && pEnd >= strSrc.c_str()) {
				pEnd--;
			}
			return std::string(strSrc.c_str(), pEnd + 1);
		}

		// trim left character c.
		inline static std::string trimLeft(const std::string& strSrc, char c) {
			const char* pStart = strSrc.c_str();
			while (*pStart == c && (pStart < strSrc.c_str() + strSrc.length())) {
				pStart++;
			}
			return std::string(pStart, strSrc.c_str() + strSrc.length());
		}

		// trim left and right space and return.
		inline static std::string trimSpace(const std::string& str) {
			const char* whitespace = " \t\n\r";
			size_t start = str.find_first_not_of(whitespace);
			if (start == std::string::npos) {
				return std::string("");
			}
			size_t end = str.find_last_not_of(whitespace);
			return str.substr(start, end - start + 1);
		}


		// split strSrc with strDelim, return from vecRet.
		inline static void strSplits(const std::string& strSrc, const std::string& strDelim, std::vector<std::string>& vecRet) {
			const char* pszNextData = strSrc.c_str();
			const char* pszPos = nullptr;
			while ((pszPos = strstr(pszNextData, strDelim.c_str())) != nullptr) {
				const auto size = (unsigned int)(pszPos - pszNextData);
				vecRet.push_back(trimSpace(std::string(pszNextData, size)));
				pszNextData = pszPos + 1;
			}

			/* push last substring only if not empty*/
			if (pszNextData != nullptr) {
				std::string strData = trimRight(pszNextData, ' ');
				if (!strData.empty()) {
					vecRet.push_back(trimSpace(strData));
				}
			}
		}

		// node information.
		struct SNodeInfo {
			// node name.
			std::string name;

			// node key to value lists.
			std::map<std::string, std::string> key2Value;

			// all children lists.
			std::map<std::string, SNodeInfo*> name2Children;
		};

        // node pair.
		typedef std::pair<std::string, bool> nodePair;

		// CConfig class.
		class CConfig final {
		public:
			CConfig();
			virtual ~CConfig();

		public:
			// parse from local file.
			bool Parse(const std::string& filePath);
			
			// parse from detail text, and with sep separation(such as \n)
			bool ParseContent(const std::string& content, const char *sep = "\n");

			// path is like a/b/c/d, return its value.
			nodePair GetNode(const std::string& path);

			// template of GetNode value of T(int,string,float,bool and so).
			template <typename T>
			T GetNode(const std::string& path, T defaultVal) {
				auto retPair = this->GetNode(path);
				if (!retPair.second) {
					return defaultVal;
				} else {
					return toUserType<T>(retPair.first);
				}
			}

			// get value and return true or false.
			template <typename T>
			bool GetNodeWithResult(const std::string& path, T &value) {
				auto retPair = this->GetNode(path);
				if (!retPair.second) {
					return false;
				} else {
					value = (T)toUserType<T>(retPair.first);
					return true;
				}
			}

			// transfer it to vector<T>.
			template <typename T>
			bool GetNode(const std::string& path, std::vector<T>& vec) {
				const std::string& node = this->GetNode<std::string>(path,"");
				if (node.empty()) {
					return false;
				}

				std::vector<std::string> res;
				strSplits(node, ",", res);
				for (int i = 0;i < res.size();i++) {
					vec.push_back(toUserType<T>(trimSpace(res[i])));
				}
				return true;
			}

			// transfer it to list<T>.
			template <typename T>
			bool GetNode(const std::string& path, std::list<T>& l) {
				const std::string& node = this->GetNode<std::string>(path, "");
				if (node.empty()) {
					return false;
				}

				std::vector<std::string> res;
				strSplits(node, ",", res);
				for (int i = 0;i < res.size();i++) {
					l.push_back(toUserType<T>(trimSpace(res[i])));
				}
				return true;
			}

			// transfer it to map<key,value>.
			template <typename key, typename value>
			bool GetNode(const std::string& path, std::map<key, value>& k2v) {
				const std::string& node = this->GetNode<std::string>(path, "");
				if (node.empty()) {
					return false;
				}

				std::vector<std::string> res;
			    strSplits(node, ",", res);
				for (int i = 0;i < res.size();i++) {
					auto& e = res[i];
					e = trimSpace(e);

					std::vector<std::string> keys;
					strSplits(e, ":", keys);
					if (keys.size() >= 2) {
						auto k = toUserType<key>(trimSpace(keys[0]));
						auto v = toUserType<value>(trimSpace(keys[1]));
						k2v[k] = v;
					}
				}
				return true;
			}

			// return last error.
			std::string GetLastError();

		protected:
			// parse line of strLine.
		    bool parseLine(std::stack<SNodeInfo*> &stk, const std::string &strLine, int &line, CDefer &defer);

			// all integral type such as int, char, short and so on.
			template <typename T>
			static typename std::enable_if<std::is_integral<T>::value, T>::type
				toUserType(const std::string& strVal) {
				try {
					return (T)std::stoll(strVal);
				} catch (...) {
					char buf[1024] = { 0 };
					std::snprintf(buf, sizeof(buf), "std::stoll transfer %s error", strVal.c_str());
					throw std::runtime_error(buf);
				}
			}

			// bool template.
			static bool toUserType(const std::string& strVal) {
				if (strVal == "true" || strVal == "TRUE") {
					return true;
				} else if (strVal == "false" || strVal == "FALSE") {
					return false;
				} else {
					if (strVal.size() > 0) {
						switch (strVal[0]) {
						case 't': case 'T': case '1':
							return true;
						case 'f': case 'F': case '0':
							return false;
						default:
							return std::stoi(strVal) > 0;
						}
					} else {
						return false;
					}
				}
			}

			// unsigned long long.
			template <typename T>
			static typename std::enable_if<std::is_same<T, unsigned long long>::value, T>::type
			    toUserType(const std::string& strVal) {
				try {
					return std::stoull(strVal);
				} catch (...) {
					char buf[1024] = { 0 };
					std::snprintf(buf, sizeof(buf), "std::stoull transfer %s error", strVal.c_str());
					throw std::runtime_error(buf);
				}
			}

			// float type.
			template <typename T>
			static typename std::enable_if<std::is_same<T, float>::value, T>::type
				toUserType(const std::string& strVal) {
				try {
					return std::stof(strVal);
				} catch (...) {
					char buf[1024] = { 0 };
					std::snprintf(buf, sizeof(buf), "std::stof transfer %s error", strVal.c_str());
					throw std::runtime_error(buf);
				}
			}

			// double type.
			template <typename T>
			static typename std::enable_if<std::is_same<T, double>::value, T>::type
				toUserType(const std::string& strVal) {
				try {
					return std::stod(strVal);
				} catch (...) {
					char buf[1024] = { 0 };
					std::snprintf(buf, sizeof(buf), "std::stod transfer %s error", strVal.c_str());
					throw std::runtime_error(buf);
				}
			}

			// std::string type.
			template <typename T>
			static typename std::enable_if<std::is_same<T, std::string>::value, T>::type
				toUserType(const std::string& strVal) {
				return strVal;
			}

		private:
			nodePair find(std::vector<std::string>& vec, const SNodeInfo* node);
			void freeNode(SNodeInfo* node);

		protected:
			// all nodes.
			SNodeInfo* m_root;

			// error string.
			char       m_error[128];
		};
	}
}